/*
 *  Queen Mary University of London MOM System
 *  
 *  Copyright Queen Mary University of London
 *  
 *  Authors:
 *  Bob Chew (bob.chew@elec.qmul.ac.uk)
 *  Beatriz Viñal Murciano (beavimu@gmail.com)
 *  John Bigham (john.bigham@elec.qmul.ac.uk)
 *  
 *  
 *  Portions of this software uses Apache Qpid
 *    http://qpid.apache.org
 *  
 *  Apache Qpid license:
 * --------------------------------------------------------------
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * -------------------------------------------------------------
 * 
 */
package users;

import java.util.HashSet;
import java.util.Set;
import java.util.UUID;

import javax.jms.JMSException;
import javax.jms.TopicSubscriber;

import org.apache.qpid.AMQException;
import org.apache.qpid.client.TopicPublisherAdapter;
import org.apache.qpid.url.URLSyntaxException;

/**
 * Generic Qpid user, with the attributes all users must have and some useful
 * methods.
 */
public abstract class User {

    /** General data for all connections. */
    /** Unique ID of the client. */
    private String clientId;
    /** Qpid username. */
    private String username;
    /** Qpid password. */
    private String password;

    /**
     * Getter for the variable 'clientId'.
     * @return variable 'clientId'
     */
    public String getClientId() {
        return clientId;
    }
    /**
     * Setter for the variable 'clientId'.
     * @param clientId  new value of the variable 'clientId'
     */
    protected void setClientId(final String clientId) {
        this.clientId = clientId;
    }
    /**
     * Getter for the variable 'username'.
     * @return variable 'username'
     */
    public String getUsername() {
        return username;
    }
    /**
     * Setter for the variable 'username'.
     * @param username  new value of the variable 'username'
     */
    public void setUsername(final String username) {
        this.username = username;
    }
    /**
     * Getter for the variable 'password'.
     * @return variable 'password'
     */
    public String getPassword() {
        return password;
    }
    /**
     * Setter for the variable 'password'.
     * @param password  new value of the variable 'password'
     */
    public void setPassword(final String password) {
        this.password = password;
    }


    /** Data for the connection with any broker in the MOM. */
    /** Name of the exchange to use. */
    private static final String MOM_EXCHANGE = "other.topic";

    /**
     * Getter for the variable 'MOM_EXCHANGE'.
     * @return variable 'MOM_EXCHANGE'
     */
    public String getMOM_EXCHANGE() {
        return MOM_EXCHANGE;
    }


    /** Data for communicating with the overlay manager. */
    /** IP address of the broker controller. */
    private static final String OM_IP = "127.0.0.1"; //TODO
    /** Exchange used in the broker controller. */
    private static final String OM_EXCHANGE = "amq.topic";
    /** Topic to send messages to the overlay manager. */
    private static final String OM_TOPIC = "brokerRequests";
    /** Topic to receive update messages from the overlay manager. */
    private String omUpdatesTopic;
    /** Topic to receive assignment messages from the overlay manager. */
    private String omAssigTopic;
    /** Message listener to update messages from the overlay manager. */
    private UpdateMessageListener omUpMsgListener;
    /** Subscriber to assignment messages from the overlay manager. */
    private TopicSubscriber omAssigSub;
    /** Publisher to send messages to the overlay manager. */
    private TopicPublisherAdapter omAssigPub;

    /**
     * Getter for the variable 'OM_IP'.
     * @return variable 'OM_IP'
     */
    public String getOM_IP() {
        return OM_IP;
    }
    /**
     * Getter for the variable 'OM_EXCHANGE'.
     * @return variable 'OM_EXCHANGE'
     */
    public String getOM_EXCHANGE() {
        return OM_EXCHANGE;
    }
    /**
     * Getter for the variable 'OM_TOPIC'.
     * @return variable 'OM_TOPIC'
     */
    public String getOM_TOPIC() {
        return OM_TOPIC;
    }
    /**
     * Getter for the variable 'omUpdatesTopic'.
     * @return variable 'omUpdatesTopic'
     */
    public String getOmUpdatesTopic() {
        return omUpdatesTopic;
    }
    /**
     * Setter for the variable 'omUpdatesTopic'.
     * @param omUpdatesTopic  new value of the variable 'omUpdatesTopic'
     */
    public void setOmUpdatesTopic(final String omUpdatesTopic) {
        this.omUpdatesTopic = omUpdatesTopic;
    }
    /**
     * Getter for the variable 'omAssigTopic'.
     * @return variable 'omAssigTopic'
     */
    public String getOmAssigTopic() {
        return omAssigTopic;
    }
    /**
     * Setter for the variable 'omAssigTopic'.
     * @param omAssigTopic  new value of the variable 'omAssigTopic'
     */
    public void setOmAssigTopic(final String omAssigTopic) {
        this.omAssigTopic = omAssigTopic;
    }
    /**
     * Getter for the variable 'omUpMsgListener'.
     * @return variable 'omUpMsgListener'
     */
    public UpdateMessageListener getOmUpMsgListener() {
        return omUpMsgListener;
    }
    /**
     * Setter for the variable 'omUpMsgListener'.
     * @param omUpMsgListener  new value of the variable 'omUpMsgListener'
     */
    public void setOmUpMsgListener(
            final UpdateMessageListener omUpMsgListener) {
        this.omUpMsgListener = omUpMsgListener;
    }
    /**
     * Getter for the variable 'omAssigSub'.
     * @return variable 'omAssigSub'
     */
    public TopicSubscriber getOmAssigSub() {
        return omAssigSub;
    }
    /**
     * Setter for the variable 'omAssigSub'.
     * @param omAssigSub  new value of the variable 'omAssigSub'
     */
    public void setOmAssigSub(final TopicSubscriber omAssigSub) {
        this.omAssigSub = omAssigSub;
    }
    /**
     * Getter for the variable 'omAssigPub'.
     * @return variable 'omAssigPub'
     */
    public TopicPublisherAdapter getOmAssigPub() {
        return omAssigPub;
    }
    /**
     * Setter for the variable 'omAssigPub'.
     * @param omAssigPub  new value of the variable 'omAssigPub'
     */
    public void setOmAssigPub(final TopicPublisherAdapter omAssigPub) {
        this.omAssigPub = omAssigPub;
    }


    /** Connection with the overlay manager. */
    private UserConnection omConnection
            = new UserConnection(this, getOM_IP(), getOM_EXCHANGE());
    /**
     * Getter for the variable 'omConnection'.
     * @return variable 'omConnection'
     */
    public UserConnection getOmConnection() {
        return omConnection;
    }
    /**
     * Setter for the variable 'omConnection'.
     * @param omConnection  new value of the variable 'omConnection'
     */
    public void setOmConnection(final UserConnection omConnection) {
        this.omConnection = omConnection;
    }


    /** Connections to application brokers. */
    private Set<UserConnection> connections = new HashSet<UserConnection>();
    /**
     * Getter for the variable 'connections'.
     * @return variable 'connections'
     */
    public Set<UserConnection> getConnections() {
        return connections;
    }
    /**
     * Setter for the variable 'connections'.
     * @param connections  new value of the variable 'connections'
     */
    public void setConnections(final Set<UserConnection> connections) {
        this.connections = connections;
    }

    /** Number of parts of assignment messages. */
    protected static final int NUM_PARTS_ASSIG_MSG = 5;
    /** Index of the primary assigned broker in an assignment message. */
    protected static final int PB_INDEX_ASSIG = 1;
    /** Index of the primary assigned port in an assignment message. */
    protected static final int PP_INDEX_ASSIG = 2;
    /** Index of the mirror assigned broker in an assignment message. */
    protected static final int MB_INDEX_ASSIG = 3;
    /** Index of the mirror assigned port in an assignment message. */
    protected static final int MP_INDEX_ASSIG = 4;

    /** Number of parts of failure messages. */
    protected static final int NUM_PARTS_FAIL_MSG = 4;
    /** Index of the new assigned broker in a failure message. */
    protected static final int BRK_INDEX_FAIL = 2;
    /** Index of the new assigned port in a failure message. */
    protected static final int PORT_INDEX_FAIL = 3;


    /**
     * Constructor for users retrieved from the database.
     */
    public User() {
        //after retrieving a user from a database, its scAssigTopic,
        //scUpdatesTopic and omUpMsgListener MUST be set manually
    }

    /**
     * Constructor for new users.
     *
     * @param username  username to use to connect to the broker
     * @param password  password to use to connect to the broker
     */
    public User(final String username, final String password) {

        setUsername(username);
        setPassword(password);

        //get a unique ID
        setClientId(UUID.randomUUID().toString());

        //set overlay manager topics
        setOmUpdatesTopic("brokerUpdates." + getClientId());
        setOmAssigTopic("brokerAssig." + getClientId());

        //set the listener to update messages from the overlay manager
        setOmUpMsgListener(new UpdateMessageListener(this));

    }

    /**
     * Closes all the connections of the user.
     * @throws JMSException  if there is any problem
     */
    public void closeConnections() throws JMSException {
        getOmConnection().closeConn();
        for (UserConnection con : getConnections()) {
            con.closeConn();
        }
    }

    /**
     * Get the connection with the given application broker.
     *
     * @param broker  to look for a connection with it
     * @return  connection with broker
     */
    protected UserConnection getConnForBroker(final String broker) {
        for (UserConnection con : getConnections()) {
            if (con.getBroker().equals(broker)) {
                return con;
            }
        }
        return null;
    }
    
    /**
     * Disconnect the given connection and remove it from the list.
     * 
     * @param conn  connection to disconnect
     */
    protected void disconnect(final UserConnection conn) {
        //remove connection from list
        getConnections().remove(conn);
        try {
            //make sure the connection is closed
            conn.closeConn();
        } catch (Exception e1) {
            //connection didn't exist or was already closed
        }
    }

    /**
     * When a user is created, it asks the overlay manager what brokers it has
     * to use.
     */
    public abstract void requestBroker();

    /**
     * Change the primary broker of a user for a topic.
     * The old mirror broker of the user becomes its new primary broker and the
     * given broker becomes its new mirror broker.
     *
     * @param topic  topic to change
     * @param newBroker  new mirror broker to use
     * @param newPort  new mirror port to use
     * @throws URLSyntaxException  if there is any problem
     * @throws JMSException  if there is any problem
     * @throws AMQException  if there is any problem
     * @throws Exception  if no new mirror broker is assigned
     */
    protected abstract void changePrimary(
            String topic, String newBroker, int newPort)
            throws URLSyntaxException, JMSException, AMQException, Exception;

    /**
     * Change the mirror broker of a user for a topic.
     * The given broker becomes its new mirror broker.
     *
     * @param topic  topic to change
     * @param newBroker  new mirror broker to use
     * @param newPort  new mirror port to use
     * @throws URLSyntaxException  if there is any problem
     * @throws JMSException  if there is any problem
     * @throws AMQException  if there is any problem
     * @throws Exception  if no new mirror broker is assigned
     */
    protected abstract void changeMirror(
            String topic, String newBroker, int newPort)
            throws URLSyntaxException, JMSException, AMQException, Exception;

}
